"""
    Copyright (C) 2009  Andrew Stanton

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

"""
from lxml import etree
import urllib
from BaseParser import BaseParser
import Marshaller
import copy
import pprint
import UserList

class Parser(object):
    """
    Common Functions that all Parsers should be able to use.
    """
    def __str__(self):
        return str(self._getDict().keys())
    
    def _getDict(self):
        return dict(((unicode(k),v) for k,v in self.__dict__.iteritems() if not k.startswith('_')))

    def __getitem__(self, key):
        """
        Dictionary-like access for all attributes.
        """
        return self.__dict__[key]

    def iteritems(self):
        """
        Iterates over the elements of the Parser.
        """
        for i in self._getDict().iteritems():
            yield i
    
    def toDict(self):
        """
        Returns a dict representation of the Parser.
        """
        dct = {}
        for k,v in self._getDict().iteritems():
            if isinstance(v, Parser):
                dct[k] = v.toDict()
            elif isinstance(v, list):
                lst = []
                for i in v:
                    if isinstance(i, Parser):
                        lst.append(i.toDict())
                    else:
                        lst.append(i)
                dct[k] = lst
            else:
                dct[k] = v
        return dct
    __iter__ = iteritems
    
    def toXML(self, f=None):
        """
        Dumps rendered object to XML.

        @param f: Renders the parser to the file.  If omitted, returns a string
        @type file-like or string.
        """
        if not f:
            return Marshaller.XMLMarshaller.dumps(self)
        else:
            Marshaller.XMLMarshaller.dump(self, file=f)
    __repr__ = __str__

    def pprint(self):
        print pprint.pprint(self.toDict())
        

class SiteParser(Parser):
    """
    Returns a parser from url.
    """
    __metaclass__ = BaseParser
    
    def __init__(self, doc = None):
        if doc:
            self.__doc__ = doc
        super(SiteParser, self).__init__()

    def __call__(self, methodDict, site, root):
        website = urllib.urlopen(site)
        parser = etree.HTMLParser(remove_blank_text=True)
        tree = etree.parse(website, parser)
        root = tree.getroot()
        for attr,method in methodDict.iteritems():
            setattr(self, attr, method(root))
        return self

class XpathParser(Parser):
    __metaclass__ = BaseParser
    
    def __init__(self, deepCopy=False):
        self.__deepCopy = deepCopy
        super(XpathParser, self).__init__()

    def __call__(self, methodDict, xpath, root):
        node =  root.xpath(xpath)
        if not node:
            # If we don't have any nodes, return None for all attributes.
            for attr in methodDict.iterkeys():
                setattr(self, attr, None)
            return self

        # If we found more than one node, we are a list
        if len(node) > 1:
            xpathLst = []
            for n in node:
                xp = XpathParser(True)
                xpathLst.append(xp(methodDict, '.', n))
            return xpathLst

        # It's a list of one, so make it singular.
        node = node[0]

        # Loop over all attributes and calculate everything.
        for attr, method in methodDict.iteritems():
            if self.__deepCopy:
                setattr(self, attr, copy.deepcopy(method(node)))
            else:
                setattr(self, attr, method(node))
        return self

class ZipParser(Parser):
    """
    This is used to zip attribute lists into several list
    objects.
    """
    __metaclass__ = BaseParser

    def __call__(self, methodDict, xpath, root):
        attrLst = []
        for a, m in methodDict.iteritems():
            attrLst.append((a, m(root)))
        # Get the maximum list length
        maxLst = max([len(l) for a,l in attrLst])
        # Extend each list to the same length.
        attrLst = [(a, l.extend([None] * (maxLst - len(l))) or l) for a,l in attrLst]
        #zip them together
        mapped = zip(*[v[1] for v in attrLst]) 
        keys = [t[0] for t in attrLst]
        lst = []
        for t in mapped:
            p = Parser()
            p.__dict__.update(dict(zip(keys,t)))
            lst.append(p)
        return lst
class FileParser(Parser):
    """
    Works like site parser, except gathers from a file instead of
    a website.
    """
    __metaclass__ = BaseParser

    def __init__(self, file=None):
        if file:
            self.__doc__ = file
        super(FileParser, self).__init__()

    def __call__(self, methodDict, filepath, root):
        input = file(filepath, 'r')
        parser = etree.HTMLParser(remove_blank_text=True)
        tree = etree.parse(input, parser)
        input.close()
        root = tree.getroot()
        for attr,method in methodDict.iteritems():
            setattr(self, attr, method(root))
        return self
